Skip to content

[CHA-2563] Add retention policy integration tests#227

Merged
itsmeadi merged 6 commits intomainfrom
feature/cha-2563-retention-policy-endpoints
Mar 31, 2026
Merged

[CHA-2563] Add retention policy integration tests#227
itsmeadi merged 6 commits intomainfrom
feature/cha-2563-retention-policy-endpoints

Conversation

@hifaizsk
Copy link
Copy Markdown
Contributor

@hifaizsk hifaizsk commented Mar 23, 2026

Ticket

Summary

  • Add integration tests for the 4 retention policy endpoints: SetRetentionPolicy, GetRetentionPolicy, GetRetentionPolicyRuns, DeleteRetentionPolicy
  • Tests cover the full CRUD lifecycle: create policy → get policy → get runs → delete policy
  • Graceful skip when retention feature is not enabled on the test app

Checklist

  • Integration tests for retention policy endpoints

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added retention policy management for chat channels with configuration, cleanup tracking, and policy lifecycle operations.
    • Introduced chat preferences support for channel type creation and updates.
    • Enhanced moderation capabilities with action logging, flag counting, and escalation controls.
    • Expanded video SIP functionality with authentication resolution, trunk management (password/IP controls), and flexible inbound routing.
    • Added comment restoration, partial updates, and collection querying in feeds.
    • Enabled external storage configuration and validation for data imports.
    • Added activity metrics configuration support for app management and admin controls for group membership.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Extended REST client APIs across chat, common, moderation, video, and feeds modules with new retention-policy endpoints, importer-storage management, SIP authentication, comment restoration, and collection queries. Updated method signatures with optional parameters for chat preferences, activity metrics, and SIP configurations. Added supporting dataclass models for retention policies and cleanup runs.

Changes

Cohort / File(s) Summary
Chat Retention Policy
getstream/chat/rest_client.py, getstream/chat/async_rest_client.py
Added four new retention-policy endpoints: get_retention_policy(), set_retention_policy(), delete_retention_policy(), and get_retention_policy_runs() for managing chat message retention policies with configurable max age and cleanup run tracking.
Chat Channel Type Updates
getstream/chat/rest_client.py, getstream/chat/async_rest_client.py
Updated create_channel_type() and update_channel_type() signatures to accept optional chat_preferences parameter and forward it into request payloads.
Common App & Importer Storage
getstream/common/rest_client.py, getstream/common/async_rest_client.py
Extended update_app() with activity_metrics_config parameter. Added four new importer external storage methods: get_importer_external_storage(), upsert_importer_external_storage(), delete_importer_external_storage(), and validate_importer_external_storage(). Updated add_user_group_members() with as_admin parameter.
Moderation Enhancements
getstream/moderation/rest_client.py, getstream/moderation/async_rest_client.py
Added insert_action_log() and get_flag_count() methods. Extended check() with optional content_published_at parameter and submit_action() with optional escalate parameter.
Video SIP Configuration
getstream/video/rest_client.py, getstream/video/async_rest_client.py
Added resolve_sip_auth() endpoint. Made called_numbers optional in update_sip_inbound_routing_rule(). Extended create_sip_trunk() and update_sip_trunk() with optional password and allowed_ips. Refactored resolve_sip_inbound() signature with optional challenge and new trunk_id parameter.
Feeds Collections & Comments
getstream/feeds/rest_client.py
Added query_collections() for collection queries. Added id_around parameter to comment retrieval. Introduced update_comment_partial() for partial comment updates and restore_comment() for comment restoration. Updated unfollow APIs to use UnfollowPair type and added keep_history parameter.
Models & Types
getstream/models/__init__.py
Added retention-policy dataclasses: RetentionPolicyConfig, RetentionPolicy, RetentionCleanupRunStats, RetentionCleanupRun. Added request/response types for retention and importer storage operations with unix-nanosecond datetime encoding/decoding.
Webhook Parsing
getstream/webhook.py, getstream/tests/test_webhook.py
Added EVENT_TYPE_FEEDS_COMMENT_RESTORED constant and CommentRestoredEvent mapping. Added test test_parse_feeds_comment_restored with 404 skip handling for unavailable endpoints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Hops and bounces through the code we go,
New retention policies steal the show!
SIP trunks, comments, storage so grand,
Building APIs across the land! 🚀✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the primary objective to add retention policy integration tests, though the changeset extends beyond tests to include API client implementations and data models.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cha-2563-retention-policy-endpoints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
getstream/models/__init__.py (1)

1825-1828: Note: Default type value applies to only one of four handled event types.

AsyncExportErrorEvent is used for export.bulk_image_moderation.error, export.channels.error, export.moderation_logs.error, and export.users.error (per getstream/webhook.py mapping). The default now specifically matches bulk_image_moderation.error.

This is fine for deserialization (the type comes from the payload), but if code instantiates this class directly without specifying type, it will default to bulk image moderation regardless of the actual error context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/models/__init__.py` around lines 1825 - 1828, The
AsyncExportErrorEvent class currently sets type: str =
dc_field(default="export.bulk_image_moderation.error",
metadata=dc_config(field_name="type")), which biases direct instantiation to the
bulk_image_moderation event; remove the hardcoded default so the field is
required (e.g., change to type: str =
dc_field(metadata=dc_config(field_name="type")) or use an explicit
empty/default-less dc_field) and add optional runtime validation if needed;
reference AsyncExportErrorEvent, the type field declaration, dc_field/dc_config
usage, and the getstream/webhook.py mapping to ensure callers supply the correct
specific event type when constructing instances.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@getstream/models/__init__.py`:
- Around line 1825-1828: The AsyncExportErrorEvent class currently sets type:
str = dc_field(default="export.bulk_image_moderation.error",
metadata=dc_config(field_name="type")), which biases direct instantiation to the
bulk_image_moderation event; remove the hardcoded default so the field is
required (e.g., change to type: str =
dc_field(metadata=dc_config(field_name="type")) or use an explicit
empty/default-less dc_field) and add optional runtime validation if needed;
reference AsyncExportErrorEvent, the type field declaration, dc_field/dc_config
usage, and the getstream/webhook.py mapping to ensure callers supply the correct
specific event type when constructing instances.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 34692f6d-7d29-403d-9747-43880155166d

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff29f0 and ceabba6.

📒 Files selected for processing (13)
  • getstream/chat/async_rest_client.py
  • getstream/chat/rest_client.py
  • getstream/common/async_rest_client.py
  • getstream/common/rest_client.py
  • getstream/feeds/rest_client.py
  • getstream/models/__init__.py
  • getstream/moderation/async_rest_client.py
  • getstream/moderation/rest_client.py
  • getstream/tests/test_webhook.py
  • getstream/video/async_rest_client.py
  • getstream/video/rest_client.py
  • getstream/webhook.py
  • tests/test_chat_misc.py

hifaizsk and others added 5 commits March 31, 2026 12:52
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regenerated SDK from latest serverside-api.yaml spec. Updated retention
policy integration test to only test get_retention_policy_runs endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@getstream/moderation/async_rest_client.py`:
- Around line 141-146: Both methods reference the
"/api/v2/moderation/moderation_rule/{id}" route but never accept or pass an id,
so the {id} placeholder can never be populated; update delete_moderation_rule
and get_moderation_rule to accept an id parameter (e.g., id: str) and call
self.delete/self.get with path_params={"id": id} (keep the
telemetry.operation_name on get_moderation_rule unchanged) so the route is
invoked with the actual id.
- Around line 62-63: The public async method signatures (notably check and
submit_action) have new optional parameters inserted mid-parameter-list which
breaks callers using positional args; move any newly added optional parameters
(e.g., content_published_at for check and escalate/submit-related flags for
submit_action) to the end of the public function signatures preserving original
parameter order, update the call that builds the request object
(CheckRequest(...) in check and the corresponding request builder in
submit_action) to use keyword arguments or the new parameter order, and ensure
type annotations and default None values remain unchanged so existing positional
callers continue to work.

In `@getstream/moderation/rest_client.py`:
- Around line 140-146: The methods delete_moderation_rule and
get_moderation_rule are missing an id parameter and are not passing path_params
for the "{id}" placeholder; update both methods (and their async counterparts)
to accept id: str (or appropriate type), include it in path_params when calling
self.delete/self.get (e.g., path_params={"id": id}), and ensure the method
signatures and return types remain the same (refer to delete_moderation_rule,
get_moderation_rule and their async versions to mirror the pattern used by
get_appeal).

In `@getstream/video/rest_client.py`:
- Around line 487-489: The signature of resolve_sip_inbound changed breaking
positional compatibility by inserting trunk_id before challenge; restore
backward-compatible parameter order by moving trunk_id to the end of the
optional parameters list in resolve_sip_inbound (and the mirrored method in
async_rest_client), so the signature becomes (sip_caller_number,
sip_trunk_number, routing_number=None, challenge=None, sip_headers=None,
trunk_id=None); update the OpenAPI codegen template that produces these methods
so future generated code keeps trunk_id last and ensure the body construction
still uses the trunk_id variable name when present.

In `@tests/test_chat_misc.py`:
- Around line 605-614: Replace the lone test_get_retention_policy_runs with a
test class that exercises the full retention-policy CRUD flow (call
client.chat.set_retention_policy, then client.chat.get_retention_policy,
client.chat.get_retention_policy_runs, and finally
client.chat.delete_retention_policy) so wiring issues in rest_client.py surface;
group these tests in a class (e.g., TestRetentionPolicyCRUD) and assert expected
responses at each step; only skip the entire class when the API returns an
explicit feature-disabled response (detect a specific error code/message from
client.chat.* calls rather than any generic "404" / "not found"), and ensure
cleanup (delete) runs when setup succeeded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bd5112e-6c63-4bf7-83e3-d01dcfbbadb2

📥 Commits

Reviewing files that changed from the base of the PR and between ca69edc and b5ca71a.

📒 Files selected for processing (18)
  • getstream/chat/async_channel.py
  • getstream/chat/async_rest_client.py
  • getstream/chat/channel.py
  • getstream/chat/rest_client.py
  • getstream/common/async_rest_client.py
  • getstream/common/rest_client.py
  • getstream/feeds/feeds.py
  • getstream/feeds/rest_client.py
  • getstream/models/__init__.py
  • getstream/moderation/async_rest_client.py
  • getstream/moderation/rest_client.py
  • getstream/tests/test_webhook.py
  • getstream/video/async_call.py
  • getstream/video/async_rest_client.py
  • getstream/video/call.py
  • getstream/video/rest_client.py
  • getstream/webhook.py
  • tests/test_chat_misc.py
✅ Files skipped from review due to trivial changes (3)
  • getstream/feeds/feeds.py
  • getstream/chat/async_channel.py
  • getstream/chat/channel.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
getstream/feeds/rest_client.py (1)

1963-1977: ⚠️ Potential issue | 🟡 Minor

Same breaking change as unfollow_batch: parameter type changed to UnfollowPair.

This has the same implications as the unfollow_batch change above. Existing callers using FollowPair will need to migrate to UnfollowPair.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/feeds/rest_client.py` around lines 1963 - 1977,
get_or_create_unfollows currently requires List[UnfollowPair], which breaks
callers still passing List[FollowPair]; update the method signature and body to
accept both types (e.g., follows: List[Union[FollowPair, UnfollowPair]] or keep
typing but detect FollowPair at runtime) and convert any FollowPair instances to
UnfollowPair before building UnfollowBatchRequest; specifically adjust
get_or_create_unfollows to handle FollowPair -> UnfollowPair conversion (or
normalization) so existing callers continue to work while new callers can pass
UnfollowPair.
♻️ Duplicate comments (2)
getstream/moderation/rest_client.py (1)

152-153: ⚠️ Potential issue | 🟠 Major

Preserve positional-call compatibility by appending new optional params at the end.

Line 152 and Line 560 add optional args mid-signature, which can misroute positional arguments in existing integrations.

Proposed signature ordering fix
 def check(
     self,
     entity_creator_id: str,
     entity_id: str,
     entity_type: str,
     config_key: Optional[str] = None,
     config_team: Optional[str] = None,
-    content_published_at: Optional[datetime] = None,
     test_mode: Optional[bool] = None,
     user_id: Optional[str] = None,
     config: Optional[ModerationConfig] = None,
     moderation_payload: Optional[ModerationPayload] = None,
     options: Optional[Dict[str, object]] = None,
     user: Optional[UserRequest] = None,
+    content_published_at: Optional[datetime] = None,
 ) -> StreamResponse[CheckResponse]:
 def submit_action(
     self,
     action_type: str,
     appeal_id: Optional[str] = None,
     item_id: Optional[str] = None,
     user_id: Optional[str] = None,
     ban: Optional[BanActionRequestPayload] = None,
     block: Optional[BlockActionRequestPayload] = None,
     custom: Optional[CustomActionRequestPayload] = None,
     delete_activity: Optional[DeleteActivityRequestPayload] = None,
     delete_comment: Optional[DeleteCommentRequestPayload] = None,
     delete_message: Optional[DeleteMessageRequestPayload] = None,
     delete_reaction: Optional[DeleteReactionRequestPayload] = None,
     delete_user: Optional[DeleteUserRequestPayload] = None,
-    escalate: Optional[EscalatePayload] = None,
     flag: Optional[FlagRequest] = None,
     mark_reviewed: Optional[MarkReviewedRequestPayload] = None,
     reject_appeal: Optional[RejectAppealRequestPayload] = None,
     restore: Optional[RestoreActionRequestPayload] = None,
     shadow_block: Optional[ShadowBlockActionRequestPayload] = None,
     unban: Optional[UnbanActionRequestPayload] = None,
     unblock: Optional[UnblockActionRequestPayload] = None,
     user: Optional[UserRequest] = None,
+    escalate: Optional[EscalatePayload] = None,
 ) -> StreamResponse[SubmitActionResponse]:

Also applies to: 166-167, 560-561, 583-584

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/moderation/rest_client.py` around lines 152 - 153, The new optional
parameters content_published_at and test_mode were inserted mid-signature and
can break positional-call compatibility; move these optional params to the end
of the function/method parameter lists wherever they appear (the signatures
shown with content_published_at/test_mode at lines around 152-153, 166-167,
560-561, and 583-584), update the function/type hints and docstrings
accordingly, and adjust any internal callers/tests that relied on the previous
positional ordering so callers continue to work with positional args.
getstream/moderation/async_rest_client.py (1)

154-155: ⚠️ Potential issue | 🟠 Major

Keep new optional params at the end of public signatures to avoid positional-call breakage.

Line 154 and Line 568 introduce new optional args in the middle of existing parameter lists. Positional callers will silently bind values to the wrong parameters.

Proposed compatibility-safe signature order
 async def check(
     self,
     entity_creator_id: str,
     entity_id: str,
     entity_type: str,
     config_key: Optional[str] = None,
     config_team: Optional[str] = None,
-    content_published_at: Optional[datetime] = None,
     test_mode: Optional[bool] = None,
     user_id: Optional[str] = None,
     config: Optional[ModerationConfig] = None,
     moderation_payload: Optional[ModerationPayload] = None,
     options: Optional[Dict[str, object]] = None,
     user: Optional[UserRequest] = None,
+    content_published_at: Optional[datetime] = None,
 ) -> StreamResponse[CheckResponse]:
 async def submit_action(
     self,
     action_type: str,
     appeal_id: Optional[str] = None,
     item_id: Optional[str] = None,
     user_id: Optional[str] = None,
     ban: Optional[BanActionRequestPayload] = None,
     block: Optional[BlockActionRequestPayload] = None,
     custom: Optional[CustomActionRequestPayload] = None,
     delete_activity: Optional[DeleteActivityRequestPayload] = None,
     delete_comment: Optional[DeleteCommentRequestPayload] = None,
     delete_message: Optional[DeleteMessageRequestPayload] = None,
     delete_reaction: Optional[DeleteReactionRequestPayload] = None,
     delete_user: Optional[DeleteUserRequestPayload] = None,
-    escalate: Optional[EscalatePayload] = None,
     flag: Optional[FlagRequest] = None,
     mark_reviewed: Optional[MarkReviewedRequestPayload] = None,
     reject_appeal: Optional[RejectAppealRequestPayload] = None,
     restore: Optional[RestoreActionRequestPayload] = None,
     shadow_block: Optional[ShadowBlockActionRequestPayload] = None,
     unban: Optional[UnbanActionRequestPayload] = None,
     unblock: Optional[UnblockActionRequestPayload] = None,
     user: Optional[UserRequest] = None,
+    escalate: Optional[EscalatePayload] = None,
 ) -> StreamResponse[SubmitActionResponse]:

Also applies to: 168-169, 568-569, 591-592

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/moderation/async_rest_client.py` around lines 154 - 155, New
optional parameters content_published_at and test_mode were inserted in the
middle of public function signatures in
getstream/moderation/async_rest_client.py which will break positional callers;
locate every public function or method in that file that currently has
content_published_at and/or test_mode in its parameter list (the occurrences
noted around the earlier diff blocks) and move those optional parameters to the
end of the parameter list (preserving Optional[datetime] and Optional[bool]
types and default None values), update any corresponding type hints and
docstrings, and ensure any internal call sites use keyword arguments or are
updated to the new ordering so existing positional calls keep the original
binding behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@getstream/feeds/rest_client.py`:
- Around line 1963-1977: get_or_create_unfollows currently requires
List[UnfollowPair], which breaks callers still passing List[FollowPair]; update
the method signature and body to accept both types (e.g., follows:
List[Union[FollowPair, UnfollowPair]] or keep typing but detect FollowPair at
runtime) and convert any FollowPair instances to UnfollowPair before building
UnfollowBatchRequest; specifically adjust get_or_create_unfollows to handle
FollowPair -> UnfollowPair conversion (or normalization) so existing callers
continue to work while new callers can pass UnfollowPair.

---

Duplicate comments:
In `@getstream/moderation/async_rest_client.py`:
- Around line 154-155: New optional parameters content_published_at and
test_mode were inserted in the middle of public function signatures in
getstream/moderation/async_rest_client.py which will break positional callers;
locate every public function or method in that file that currently has
content_published_at and/or test_mode in its parameter list (the occurrences
noted around the earlier diff blocks) and move those optional parameters to the
end of the parameter list (preserving Optional[datetime] and Optional[bool]
types and default None values), update any corresponding type hints and
docstrings, and ensure any internal call sites use keyword arguments or are
updated to the new ordering so existing positional calls keep the original
binding behavior.

In `@getstream/moderation/rest_client.py`:
- Around line 152-153: The new optional parameters content_published_at and
test_mode were inserted mid-signature and can break positional-call
compatibility; move these optional params to the end of the function/method
parameter lists wherever they appear (the signatures shown with
content_published_at/test_mode at lines around 152-153, 166-167, 560-561, and
583-584), update the function/type hints and docstrings accordingly, and adjust
any internal callers/tests that relied on the previous positional ordering so
callers continue to work with positional args.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 365f11b6-7dae-4afd-9dd3-02471e4d5259

📥 Commits

Reviewing files that changed from the base of the PR and between b5ca71a and edf991c.

📒 Files selected for processing (12)
  • getstream/chat/async_rest_client.py
  • getstream/chat/rest_client.py
  • getstream/common/async_rest_client.py
  • getstream/common/rest_client.py
  • getstream/feeds/rest_client.py
  • getstream/models/__init__.py
  • getstream/moderation/async_rest_client.py
  • getstream/moderation/rest_client.py
  • getstream/tests/test_webhook.py
  • getstream/video/async_rest_client.py
  • getstream/video/rest_client.py
  • getstream/webhook.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • getstream/webhook.py
  • getstream/chat/rest_client.py
  • getstream/video/async_rest_client.py

Remove duplicate AsyncExportErrorEvent imports in webhook.py,
remove unused json import in test_webhook.py, and apply ruff formatting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants